/**
 * DVN: Distributed Virtual Network Simulation Platform
 * Copyright (c) 2006-2009 Tyson Malchow <tyson.malchow@gmail.com>
 *
 * This file is part of the DVN distributed virtual network simulation
 * program.
 *
 * DVN is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * DVN is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with DVN.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "global.h"
#include "vci.h"
#include "sysconfig.h"
#include "log.h"
#include "rand.h"
#include "context.h"
#include "module.h"
#include "routing.h"
#include "netconst.h"
#include "context.h"

/* forward static decls */
static void vci_free_network_connection(void * vcnn, int netid);
static void vci_free_network(void * vnet, int netid) ;
static void vci_free_socket(void * vsocket, int socketid);
static void vci_free_mailbox(void * vmbox, int laddr);


vci_addressing_scheme_tp vci_create_addressing_scheme (int num_slaves, int max_wrkr_per_slave) {
	vci_addressing_scheme_tp scheme;
	unsigned int slave_bit_count;
	unsigned int worker_bit_count;

	scheme = malloc(sizeof(*scheme));
	if(!scheme)
		printfault(EXIT_NOMEM, "vci_create_addressing_scheme: Out of memory");

	slave_bit_count = ceil(log(num_slaves) / log(2));
	scheme->slave_mask = (uint32_t) pow(2, slave_bit_count) - 1;

	worker_bit_count = ceil(log(max_wrkr_per_slave) / log(2));
	scheme->worker_mask = (uint32_t) pow(2, worker_bit_count) - 1;
	scheme->worker_mask <<= slave_bit_count;
	scheme->worker_shiftcount = slave_bit_count;

	scheme->node_shiftcount = worker_bit_count + slave_bit_count;
	scheme->node_randmax = pow(2, 32 - scheme->node_shiftcount) - 1;

	return scheme;
}

void vci_destroy_addressing_scheme (vci_addressing_scheme_tp scheme) {
	free(scheme);
}

unsigned int vci_ascheme_get_worker (vci_addressing_scheme_tp scheme, in_addr_t ip) {
	return ((ip & scheme->worker_mask) >> scheme->worker_shiftcount);
}

unsigned int vci_ascheme_get_slave (vci_addressing_scheme_tp scheme, in_addr_t ip) {
	return (ip & scheme->slave_mask);
}

unsigned int vci_ascheme_get_node (vci_addressing_scheme_tp scheme, in_addr_t ip) {
	return (ip >> scheme->node_shiftcount);
}

unsigned int vci_ascheme_rand_node (vci_addressing_scheme_tp scheme) {
	unsigned int node;
	unsigned char high_order;

	do {
		node = dvn_rand_fast(scheme->node_randmax);
		high_order = node >> (24 - scheme->node_shiftcount);
	} while(high_order == 0 || high_order == 0xff);

	return node;
}

in_addr_t vci_ascheme_build_addr(vci_addressing_scheme_tp scheme, unsigned int slave_id, unsigned int worker_id, unsigned int node_id) {
	return slave_id + (worker_id << scheme->worker_shiftcount) + (node_id << scheme->node_shiftcount);
	//return node_id + (worker_id << scheme->worker_shiftcount) + (slave_id  << scheme->slave_shiftcount);
}

vci_mgr_tp vci_mgr_create (events_tp events, unsigned int slave_id, unsigned int worker_id, vci_addressing_scheme_tp scheme) {
	vci_mgr_tp rv = malloc(sizeof(*rv));

	rv->ascheme = scheme;
	rv->events = events;

	rv->networks =  hashtable_create(sysconfig_get_int("vci_network_hashsize"), sysconfig_get_float("vci_network_hashgrowth"));
	rv->mailboxes = hashtable_create(sysconfig_get_int("vci_mailbox_hashsize"), sysconfig_get_float("vci_mailbox_hashgrowth"));

	rv->slave_id = slave_id;
	rv->worker_id = worker_id;

	rv->c_adr_cnt = 0;
	rv->last_remote_sent_at = PTIME_INVALID;

	return rv;
}

ptime_t vci_get_last_processed(vci_mgr_tp mgr) {
	return mgr->last_remote_sent_at;
}

static void vci_free_network_connection(void * vcnn, int netid) {
	vci_network_connection_tp cnn = vcnn;

	if(cnn)
		free(cnn);

	return;
}

static void vci_free_network(void * vnet, int netid) {
	vci_network_tp net = vnet;

	if(net) {
		hashtable_walk(net->remote_connections, &vci_free_network_connection);
		hashtable_destroy(net->remote_connections);
		free(net);
	}

	return;
}

static void vci_free_socket(void * vsocket, int socketid) {
	vci_socket_tp socket = vsocket;
	if(socket)
		free(socket);
	return;
}

static void vci_free_mailbox(void * vmbox, int laddr) {
	vci_mailbox_tp mbox = vmbox;

	if(mbox) {
		/* call event */
		context_execute_destroy(mbox->context_provider);

		/* delete module memory */
		module_destroy_instance(mbox->context_provider->modinst);

		/* free the context provider data */
		free(mbox->context_provider);

		/* clean up its VCI elements */
		btree_walk(mbox->sockets,&vci_free_socket);
		btree_destroy(mbox->sockets);

		free(mbox);
	}

	return;
}

void vci_free_ip(vci_mgr_tp mgr, in_addr_t addr) {
	unsigned int laddr = vci_ascheme_get_node(mgr->ascheme, addr);
	vci_mailbox_tp mbox = hashtable_remove(mgr->mailboxes, laddr);
	if(mbox)
		vci_free_mailbox(mbox, laddr);
	return;
}

void vci_mgr_destroy(vci_mgr_tp mgr) {
	/* network cleanup */
	hashtable_walk(mgr->networks, &vci_free_network);
	hashtable_destroy(mgr->networks);

	/* mailbox cleanup */
	hashtable_walk(mgr->mailboxes, &vci_free_mailbox);
	hashtable_destroy(mgr->mailboxes);

	free(mgr);
	return;
}

in_addr_t vci_create_ip(vci_mgr_tp mgr, int net_id, context_provider_tp cp) {
	uint32_t laddr;
	in_addr_t addr;
	vci_network_tp network = hashtable_get(mgr->networks, net_id);
	vci_mailbox_tp mbox;

	if(!network)
		return INADDR_NONE;

	do {
		laddr = vci_ascheme_rand_node(mgr->ascheme);
	} while(hashtable_get(mgr->mailboxes, laddr));

	addr = vci_ascheme_build_addr(mgr->ascheme, mgr->slave_id, mgr->worker_id, laddr);

	mbox = malloc(sizeof(*mbox));
	if(!mbox)
		printfault(EXIT_NOMEM, "Out of memory: vci_create_ip");

	mbox->context_provider = cp;
	mbox->network_membership = network;
	mbox->sockets = btree_create(1);

	/* track it with a hashtable */
	hashtable_set(mgr->mailboxes, laddr, mbox);

	return addr;
}


vci_mailbox_tp vci_get_mailbox(vci_mgr_tp mgr, in_addr_t ip) {
	return hashtable_get(mgr->mailboxes, vci_ascheme_get_node(mgr->ascheme,ip));
}

int vci_create_datagram_socket(vci_mgr_tp mgr, in_addr_t ip, unsigned int port, vci_onrecv_cb_tp callback) {
	vci_socket_tp sock;
	vci_mailbox_tp mbox = vci_get_mailbox(mgr, ip);

	if(mbox && callback && port && port <= 65535 && !btree_get(mbox->sockets,port)) {
		sock = malloc(sizeof(*sock));

		if(!sock)
			printfault(EXIT_NOMEM, "Out of memory: vci_create_socket");

		sock->callback = callback;
		btree_insert(mbox->sockets, port, sock);

		return port;
	}

	return -1;
}

void vci_close_socket(vci_mgr_tp mgr, in_addr_t ip, int socketid) {
	vci_mailbox_tp mbox = vci_get_mailbox(mgr, ip);
	vci_socket_tp sock;

	if(!mbox)
		return;

	sock = btree_remove(mbox->sockets, socketid);
	if(sock)
		free(sock);
}

/**
 * Provides the underlying model for the network layer
 *
 * Based on the delay measurements in turbo-King
 * http://inl.info.ucl.ac.be/blogs/08-04-23-turbo-king-framework-large-scale-internet-delay-measurements
 * Paper:  http://irl.cs.tamu.edu/people/derek/papers/infocom2008.pdf
 * Note that we are looking mostly at link delay since we are modeling an inter AS delay
 *
 * We expect a CDF as follows:

   1|                         +++++++++++++++
    |                     +++
    |                  ++
    |                 +
    |                +
    |                +
    |                +
    |                +
    |                +
    |                +
    |               +
    |               +
    |              +
   0+++++++++++++++-----------------------------
    0                |
                Base Delay
                 |<----->|<----------|
                  Width      Tail
 */
unsigned int vci_model_delay(vci_netmodel_tp netmodel) {
/*	int i;
	int64_t v = 131072;
	int_fast32_t rwidth;

	for(i = 0; i < VCI_NETMODEL_TIGHTNESS_FACTOR; i++) {
		v *= (131072 - dvn_rand_fast(262144));
		v >>= 17 ;
	}

	if(v < 0)
		rwidth = (v * netmodel->width) >> 17;
	else
		rwidth = (v * netmodel->tail_width) >> 17;

	return (unsigned int) netmodel->base_delay + rwidth;*/
  float flBase = 1.0f;
  float flRandWidth = 0.0f;
  int i = 0;
  
  for(i=0;i<=VCI_NETMODEL_TIGHTNESS_FACTOR ;i++)
    {
      // Cummulatively computes random delay values
      flBase = flBase * (1.0f - (dvn_rand_fast(RAND_MAX) / ((float)RAND_MAX/2)));
    }

  if(flBase < 0)
    flRandWidth = flBase * netmodel->width;// Scales it to the desired width
  else
    flRandWidth = flBase * netmodel->tail_width;// Models the long tail
    
  return (unsigned int) netmodel->base_delay + (unsigned int)flRandWidth;
}

int vci_send (
		vci_mgr_tp mgr,
		ptime_t curtime,
		in_addr_t source_addr,
		int from_socket_id,
		in_addr_t to_addr,
		int to_port,
		const char * data,
		const unsigned int size ) {

	unsigned int target_slave_identifier;
	unsigned int target_worker_identifier;
	vci_network_tp source_net;
	vci_mailbox_tp source_mb;
	vci_socket_tp source_socket;

	if((size > 0 && data == NULL) || size > VCI_MAX_DATAGRAM_SIZE)
		return 0;

	source_mb = hashtable_get(mgr->mailboxes, vci_ascheme_get_node(mgr->ascheme, source_addr));
	if(!source_mb)
		return 0; /* this would be odd */
	source_net = source_mb->network_membership;

	source_socket = btree_get(source_mb->sockets, from_socket_id);
	if(!source_socket)
		return 0; /* a stupid module tried sending something on a non-socket */

	target_slave_identifier = vci_ascheme_get_slave(mgr->ascheme, to_addr);
	target_worker_identifier = vci_ascheme_get_worker(mgr->ascheme, to_addr);

	/* delivery on this machine? */
	if(target_slave_identifier == mgr->slave_id && target_worker_identifier == mgr->worker_id) {
		vci_msg_tp message;
		vci_socket_tp dest_socket;
		vci_mailbox_tp dest_mb;
		vci_network_tp dest_net;
		vci_netmodel_tp netmodel;

		message = malloc(sizeof(*message));

		if(!message)
			printfault(EXIT_NOMEM, "Out of memory: vci_send");

		if(size > 0) {
			message->data = malloc(size);
			if(!message->data)
				printfault(EXIT_NOMEM, "Out of memory: vci_send");
			memcpy(message->data, data, size);
		} else
			message->data = NULL;

		message->from_addr = source_addr;
		message->from_port = from_socket_id;
		message->to_port = to_port;
		message->to_addr = to_addr;
		message->data_size = size;
		message->deliver_time = curtime;
		message->network_source_id = source_net->netid;

		/* get desination mailbox and network */
		dest_mb = hashtable_get(mgr->mailboxes, vci_ascheme_get_node(mgr->ascheme, to_addr));
		if(!dest_mb)
			goto message_cleanup;
		dest_net = dest_mb->network_membership;

		/* get destination socket */
		dest_socket = btree_get(dest_mb->sockets, to_port);
		if(!dest_socket)
			goto message_cleanup;

		/* figure out the network model */
		if(dest_net == source_net)
			netmodel = &source_net->netmodel;

		else {
			vci_network_connection_tp connection;
			connection = hashtable_get(source_net->remote_connections, dest_net->netid);

			if(!connection || !connection->active)
				goto message_cleanup;

			netmodel = &connection->netmodel;
		}

		/* reliability roll.. */
		if(netmodel->reliability < VCI_RLBLTY_100) {
			unsigned int roll = dvn_rand(VCI_RLBLTY_100);
			if(roll > netmodel->reliability) /* DROP */
				goto message_cleanup;
		}

		/* network delay */
		message->deliver_time += vci_model_delay(netmodel);

		/* schedule */
		events_schedule(mgr->events, message->deliver_time, message, EVENTS_TYPE_VCI);
		message = NULL; /* prevent cleanup */

message_cleanup:
		if(message) {
			if(message->data)
				free(message->data);
			free(message);
		}

	} else {
		nbdf_tp outbound_nb;

		outbound_nb = nbdf_construct("aiaiitb",
				source_addr,
				from_socket_id,
				to_addr,
				to_port,
				source_net->netid,
				curtime,
				size,
				data);

		/* remote delivery */
		if(target_slave_identifier != mgr->slave_id)
			dvn_packet_route(DVNPACKET_SLAVE, DVNPACKET_LAYER_SIM, target_slave_identifier, SIM_FRAME_VCIDELIV, outbound_nb);

		/* delivery on a diff process on this slave */
		else
			dvn_packet_route(DVNPACKET_WORKER, DVNPACKET_LAYER_SIM, target_worker_identifier, SIM_FRAME_VCIDELIV, outbound_nb);


		nbdf_free(outbound_nb);
	}

	return 1;
}

int vci_deposit (
		vci_mgr_tp mgr,
		nbdf_tp frame) {

	vci_netmodel_tp netmodel;
	vci_network_tp source_network, dest_network;
	vci_mailbox_tp dest_mbox;
	vci_msg_tp message;

	message = malloc(sizeof(*message));
	if(!message)
		printfault(EXIT_NOMEM, "vci_deposit: Out of memory");

	nbdf_read(frame, "aiaiitB",
		&message->from_addr, &message->from_port,
		&message->to_addr, &message->to_port,
		&message->network_source_id,
		&message->deliver_time,
		&message->data_size,
		&message->data);

	if(vci_ascheme_get_slave(mgr->ascheme, message->to_addr) != mgr->slave_id ||
			vci_ascheme_get_worker(mgr->ascheme, message->to_addr) != mgr->worker_id)
		goto message_cleanup;

	source_network = hashtable_get(mgr->networks, message->network_source_id);
	if(!source_network)
		goto message_cleanup;

	dest_mbox = vci_get_mailbox(mgr, message->to_addr);
	if(!dest_mbox)
		goto message_cleanup;

	dest_network = dest_mbox->network_membership;

	if(dest_network == source_network)
		netmodel = &source_network->netmodel;

	else {
		vci_network_connection_tp connection;
		connection = hashtable_get(source_network->remote_connections, dest_network->netid);

		if(!connection || !connection->active)
			goto message_cleanup;

		netmodel = &connection->netmodel;
	}

	/* reliability roll.. */
	if(netmodel->reliability < VCI_RLBLTY_100) {
		unsigned int roll = dvn_rand(VCI_RLBLTY_100);
		if(roll > netmodel->reliability) /* DROP */
			goto message_cleanup;
	}

	/* network delay */
	message->deliver_time += vci_model_delay(netmodel);

	/* schedule */
	events_schedule(mgr->events, message->deliver_time, message, EVENTS_TYPE_VCI);
	message = NULL;

message_cleanup:
	if(message) {
		if(message->data)
			free(message->data);
		free(message);
	}

	return 1;
}


vci_network_tp vci_network_create(vci_mgr_tp mgr, unsigned int id, unsigned int base_delay, unsigned int width, unsigned int tail_width, unsigned int reliability) {
	vci_network_tp net;

	net = malloc(sizeof(*net));

	if(!net)
		printfault(EXIT_NOMEM, "vci_network_create: Out of memory");

	net->active = 1;
	net->netmodel.base_delay = base_delay;
	net->netmodel.width = width;
	net->netmodel.tail_width = tail_width;
	net->netmodel.reliability = reliability;

	net->remote_connections = hashtable_create(sysconfig_get_int("vci_rcnn_hashsize"), sysconfig_get_float("vci_rcnn_hashgrowth"));
	net->netid = id;

	hashtable_set(mgr->networks, id, net);

	return net;
}


void vci_network_connect( vci_mgr_tp mgr, int netid1, int netid2,
									unsigned int base_delay,
									unsigned int width,
									unsigned int tail_width,
									unsigned int reliability) {

	vci_network_tp net1, net2;
	vci_network_connection_tp cnn1, cnn2;

	net1 = hashtable_get(mgr->networks, netid1);
	net2 = hashtable_get(mgr->networks, netid2);

	if(!net1 || !net2)
		return;

	cnn1 = hashtable_get(net1->remote_connections, netid2);
	if(!cnn1) {
		cnn1 = malloc(sizeof(*cnn1));
		if(!cnn1)
			printfault(EXIT_NOMEM, "Out of memory: vci_network_connect");
		hashtable_set(net1->remote_connections, netid2, cnn1);
	}

	cnn2 = hashtable_get(net2->remote_connections, netid1);
	if(!cnn2) {
		cnn2 = malloc(sizeof(*cnn2));
		if(!cnn2)
			printfault(EXIT_NOMEM, "Out of memory: vci_network_connect");
		hashtable_set(net2->remote_connections, netid1, cnn2);
	}

	cnn1->active = cnn2->active = 1;
	cnn1->netmodel.base_delay = cnn2->netmodel.base_delay = base_delay;
	cnn1->netmodel.width = cnn2->netmodel.width = width;
	cnn1->netmodel.tail_width = cnn2->netmodel.tail_width = tail_width;
	cnn1->netmodel.tail_width = cnn2->netmodel.tail_width = tail_width;
	cnn1->netmodel.reliability = cnn2->netmodel.reliability = reliability;

	return;
}

void vci_network_disconnect( vci_mgr_tp mgr, int netid1, int netid2 ) {
	vci_network_tp net1, net2;
	vci_network_connection_tp cnn1, cnn2;

	net1 = hashtable_get(mgr->networks, netid1);
	net2 = hashtable_get(mgr->networks, netid2);

	if(!net1 || !net2)
		return;

	cnn1 = hashtable_get(net1->remote_connections, netid2);
	cnn2 = hashtable_get(net2->remote_connections, netid1);

	if(cnn1)
		cnn1->active = 0;

	if(cnn2)
		cnn2->active = 0;

	return;
}

void vci_free_event(void * event) {
	vci_msg_tp message = event;
	if(!message)
		return;
	if(message->data)
		free(message->data);
	free(message);
	return;
}


void vci_exec_event (vci_mgr_tp mgr, void * event) {
	vci_msg_tp message = event;
	vci_mailbox_tp mbox;
	vci_socket_tp deliv_socket;

	if(!message)
		return;

	mbox = vci_get_mailbox(mgr, message->to_addr);
	if(!mbox)
		goto message_cleanup;

	deliv_socket = btree_get(mbox->sockets, message->to_port);
	if(!deliv_socket)
		goto message_cleanup;

	/* perform the callback in-context */
	context_execute_vci_onrecv(mbox->context_provider,
			deliv_socket->callback,
			message->to_port, message->from_addr, message->from_port, message->data_size, message->data);

message_cleanup:
	if(message->data)
		free(message->data);
	free(message);

	return;
}

